// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Threading;
using System.Reflection;
using CIP4.Common;
using CIP4.Query;
using CIP4.Item;
using CIP4.Serializer;
using CIP4.RulesEngine;

namespace CIP4.Simple
{
    public abstract class StrongWrapperBase
    {
        public static Dictionary<string, IWeakWrapper> Wrappers = new Dictionary<string, IWeakWrapper>();

        internal static string GetWrapperLabel<TSomeType>(CIP4ItemAttribute itemAttribute)
        {
            return string.Format("{0}:{1}", itemAttribute.Collection, typeof (TSomeType).Name);
        }

        internal static string GetWrapperLabel<TSomeType>(string targetCollection)
        {
            return string.Format("{0}:{1}", targetCollection, typeof(TSomeType).Name);
        }

        /// <summary>
        /// Use this factory method if you want the wrapper to be cached and usable by CIP4ReferenceType instances
        /// </summary>
        public static StrongWrapper<TSomeType> GetWrapper<TSomeType>(CIP4ItemAttribute itemAttribute, LoggerDelegate logger) where TSomeType : class
        {
            if (Wrappers.ContainsKey(GetWrapperLabel<TSomeType>(itemAttribute)))
                return (StrongWrapper<TSomeType>)Wrappers[GetWrapperLabel<TSomeType>(itemAttribute)];
            StrongWrapper<TSomeType> wrapper = new StrongWrapper<TSomeType>(itemAttribute, logger);
            Wrappers.Add(GetWrapperLabel<TSomeType>(itemAttribute), wrapper);
            return wrapper;
        }

        /// <summary>
        /// Use this factory method if you want the wrapper to be cached and usable by CIP4ReferenceType instances
        /// </summary>
        public static StrongWrapper<TSomeType> GetWrapper<TSomeType>(GetConnectionValuesDelegate getValues, LoggerDelegate logger) where TSomeType : class
        {
            CIP4ItemAttribute itemAttribute = CIP4ItemAttribute.GetAttribute(typeof(TSomeType));
            if (Wrappers.ContainsKey(GetWrapperLabel<TSomeType>(itemAttribute)))
                return (StrongWrapper<TSomeType>)Wrappers[GetWrapperLabel<TSomeType>(itemAttribute)];
            StrongWrapper<TSomeType> wrapper = new StrongWrapper<TSomeType>(getValues, logger);
            Wrappers.Add(GetWrapperLabel<TSomeType>(itemAttribute), wrapper);
            return wrapper;
        }

        /// <summary>
        /// Use this factory method if you want the wrapper to be cached and usable by CIP4ReferenceType instances
        /// </summary>
        public static StrongWrapper<TSomeType> GetWrapper<TSomeType>(CIP4ConnectionAttribute connection, LoggerDelegate logger) where TSomeType : class
        {
            CIP4ItemAttribute itemAttribute = CIP4ItemAttribute.GetAttribute(typeof(TSomeType));
            if (Wrappers.ContainsKey(GetWrapperLabel<TSomeType>(itemAttribute)))
                return (StrongWrapper<TSomeType>)Wrappers[GetWrapperLabel<TSomeType>(itemAttribute)];
            StrongWrapper<TSomeType> wrapper = new StrongWrapper<TSomeType>(connection, logger);
            Wrappers.Add(GetWrapperLabel<TSomeType>(itemAttribute), wrapper);
            return wrapper;
        }

        /// <summary>
        /// Use this factory method if you want the wrapper to be cached and usable by CIP4ReferenceType instances
        /// </summary>
        public static StrongWrapper<TSomeType> GetWrapper<TSomeType>(LoggerDelegate logger) where TSomeType : class
        {
            CIP4ItemAttribute itemAttribute = CIP4ItemAttribute.GetAttribute(typeof(TSomeType));
            if (Wrappers.ContainsKey(GetWrapperLabel<TSomeType>(itemAttribute)))
                return (StrongWrapper<TSomeType>)Wrappers[GetWrapperLabel<TSomeType>(itemAttribute)];
            StrongWrapper<TSomeType> wrapper = new StrongWrapper<TSomeType>(logger);
            Wrappers.Add(GetWrapperLabel<TSomeType>(itemAttribute), wrapper);
            return wrapper;
        }

        /// <summary>
        /// Especially for those adventurous souls that want to switch connections mid-stream - this will force
        /// all subsequent references to go to the newly established connection - could have some very odd results
        /// Use this factory method if you want the wrapper to be cached and usable by CIP4ReferenceType instances
        /// </summary>
        public static StrongWrapper<TSomeType> ReplaceWrapper<TSomeType>(CIP4ItemAttribute itemAttribute, LoggerDelegate logger) where TSomeType : class
        {
            if (Wrappers.ContainsKey(GetWrapperLabel<TSomeType>(itemAttribute)))
                Wrappers.Remove(GetWrapperLabel<TSomeType>(itemAttribute));
            return GetWrapper<TSomeType>(itemAttribute, logger);
        }

        /// <summary>
        /// Especially for those adventurous souls that want to switch connections mid-stream - this will force
        /// all subsequent references to go to the newly established connection - could have some very odd results
        /// Use this factory method if you want the wrapper to be cached and usable by CIP4ReferenceType instances
        /// </summary>
        public static StrongWrapper<TSomeType> ReplaceWrapper<TSomeType>(GetConnectionValuesDelegate getValues, LoggerDelegate logger) where TSomeType : class
        {
            CIP4ItemAttribute itemAttribute = CIP4ItemAttribute.GetAttribute(typeof(TSomeType));
            if (Wrappers.ContainsKey(GetWrapperLabel<TSomeType>(itemAttribute)))
                Wrappers.Remove(GetWrapperLabel<TSomeType>(itemAttribute));
            return GetWrapper<TSomeType>(getValues, logger);
        }

        /// <summary>
        /// Especially for those adventurous souls that want to switch connections mid-stream - this will force
        /// all subsequent references to go to the newly established connection - could have some very odd results
        /// Use this factory method if you want the wrapper to be cached and usable by CIP4ReferenceType instances
        /// </summary>
        public static StrongWrapper<TSomeType> ReplaceWrapper<TSomeType>(CIP4ConnectionAttribute connection, LoggerDelegate logger) where TSomeType : class
        {
            CIP4ItemAttribute itemAttribute = CIP4ItemAttribute.GetAttribute(typeof(TSomeType));
            if (Wrappers.ContainsKey(GetWrapperLabel<TSomeType>(itemAttribute)))
                Wrappers.Remove(GetWrapperLabel<TSomeType>(itemAttribute));
            return GetWrapper<TSomeType>(connection, logger);
        }

        /// <summary>
        /// Especially for those adventurous souls that want to switch connections mid-stream - this will force
        /// all subsequent references to go to the newly established connection - could have some very odd results
        /// Use this factory method if you want the wrapper to be cached and usable by CIP4ReferenceType instances
        /// </summary>
        public static StrongWrapper<TSomeType> ReplaceWrapper<TSomeType>(LoggerDelegate logger) where TSomeType : class
        {
            CIP4ItemAttribute itemAttribute = CIP4ItemAttribute.GetAttribute(typeof(TSomeType));
            if (Wrappers.ContainsKey(GetWrapperLabel<TSomeType>(itemAttribute)))
                Wrappers.Remove(GetWrapperLabel<TSomeType>(itemAttribute));
            return GetWrapper<TSomeType>(logger);
        }

        public int PageSize { get; set; }

        public abstract IEnumerable<ExternalIDType> ExternalIdsForItems(IEnumerable<object> items);

        public abstract ISerializerType GetSerializer(SerializerKindEnum serializerKind);
    }

    public class StrongWrapper<TAType> : StrongWrapperBase, IWeakWrapper, IDisposable
    {
        #region public

        /// <summary>
        /// This is an implementation of IOrderedQueryable and can be used in Linq queries, for example 
        /// 
        ///             SimpleWrapper&lt;SpecialUsAddressType&gt; wrapper = new SimpleWrapper&lt;SpecialUsAddressType&gt;();
        ///             
        ///             // Linq does "contains lakeside", CIP4 does "= Seattle"
        ///             var query = from item in wrapper.Items
        ///                         where item.Street.Contains("Lakeside") && item.City == "Seattle"
        ///                         select item;
        ///             
        ///             foreach (SpecialUsAddressType address in query)
        ///                 Console.WriteLine(address.Zip);
        ///             
        /// </summary>
        public CIP4QueryType<TAType> Items
        {
            get
            {
                return new CIP4QueryType<TAType>();
            }
        }

        private CIP4ConnectionAttribute _constructorConnectionAttribute; 
        /// <summary>
        /// The CIP4ConnectionAttribute provided either by the type declaration or by the constructor. The values 
        /// supplied may be overridden by the GetValues delegate call if a GetValuesDelegate is supplied
        /// </summary>
        public CIP4ConnectionAttribute ClassConnectionAttribute
        {
            get
            {
                object[] atts = typeof(TAType).GetCustomAttributes(typeof(CIP4ConnectionAttribute), true);
                if (atts.Length > 0)
                {
                    return (CIP4ConnectionAttribute)atts[0];
                }
                return null;
            }
        }

        public CollectionType WrapperCollection{get; private set;}

        public CIP4ConnectionAttribute WellConnection
        {
            get
            {
                if (GetValues == null && ClassConnectionAttribute == null && _constructorConnectionAttribute == null)
                    Globals.LoggerWrapper(MajorEnum.Error, MinorEnum.InsufficientInformationToBuildAConnection,
                        "There is not enough information to build a connection for {0} - either the type must have a CIP4ConnectionAttribute or you must provide CIP4ConnectionAttribute to the wrapper constructor, or you must provide a GetValues delegate", new object[] { typeof(TAType).Name }, Logger);
                return CIP4ConnectionAttribute.GetActualConnection(GetValues, _constructorConnectionAttribute, ClassConnectionAttribute);
            }
        }

        public string WrapperCollectionName
        {
            get
            {
                if (ItemAttribute != null)
                    return ItemAttribute.Collection;
                
                Globals.LoggerWrapper(MajorEnum.Warning, MinorEnum.NoCollectionSpecified, 
                    "The type {0} does not specify a collection so it's not clear where its instances should go. Allocating the arbitrary name {1}",
                    new object[] { typeof(TAType).Name, typeof(TAType).Name + "_Collection" }, Logger);
                return typeof(TAType).Name + "_Collection";
            }
        }

        private CIP4ItemAttribute _itemAttribute;
        public CIP4ItemAttribute ItemAttribute
        {
            get
            {
                // If no ItemAttribute has been explicitly assigned through the constructor, then return 
                // the ItemAttribute attached to the type parameter. If the supplied ItemAttribute does 
                // not have an ActualName, use the type parameter name. This rigamarole is intended to allow the 
                // user to override the ItemAttribute supplied at design time in the type declaration by providing 
                // an ItemAttribute at construction time.
                if (_itemAttribute == null)
                {
                    CIP4ItemAttribute att = CIP4ItemAttribute.GetAttribute(typeof(TAType));
                    if (att == null)
                        throw new ArgumentException(string.Format("Neither the wrapper nor the type have an asociated CIP4ItemAttribute - so don't know how to handle the type {0}", typeof(TAType).Name));
                    if (string.IsNullOrEmpty(att.ActualName))
                        att.ActualName = typeof(TAType).Name;
                    if (string.IsNullOrEmpty(att.Collection))
                        att.Collection = att.ActualName + "_Collection";
                    return att;
                }
                return _itemAttribute;
            }
            set
            {
                _itemAttribute = value;
                if (string.IsNullOrEmpty(_itemAttribute.ActualName))
                    _itemAttribute.ActualName = typeof(TAType).Name;
            }
        }

        public GetConnectionValuesDelegate GetValues { get; set; }

        public LoggerDelegate Logger { get; set; }

        string _serializerLocation;
        public string SerializerLocation
        {
            get
            {
                if (string.IsNullOrEmpty(_serializerLocation))
                    _serializerLocation = Path.GetDirectoryName(typeof(TAType).Assembly.Location);
                return _serializerLocation;
            }
            set
            {
                _jsonSerializer = null;
                _xmlSerializer = null;
                _serializerLocation = value;
            }
        }

        private bool _checkItemVersionDuringSave;
        /// <summary>
        /// If this is true, the provider is required to check the version of the itme being saved against the version
        /// of any previous copy of the item. If the previous copy version is after the current copy version, the
        /// item is assumed to have been changed since it was last fetched from the store and the save will fail. This
        /// can only be true if the type associated with the wrapper declares a property with the CIP4ItemVersionAttribute.
        /// </summary>
        public bool CheckItemVersionDuringSave 
        {
            get
            {
                return _checkItemVersionDuringSave;
            }
            set
            {
                if (_checkItemVersionDuringSave == value)
                    return;
                if (!value)
                    _checkItemVersionDuringSave = false;
                else
                {
                    if (typeof (TAType).GetProperties().Any(prop => CIP4ItemVersionAttribute.GetAttribute(prop) != null))
                    {
                        _checkItemVersionDuringSave = true;
                        return;
                    }
                }
            }
        }

        public bool QueryShouldFailOnLinerSearch { get; set; }

        public StrongWrapper(LoggerDelegate logger)
        {
            Initialize(null, null, logger);
        }

        public StrongWrapper(GetConnectionValuesDelegate getValues, LoggerDelegate logger)
        {
            GetValues = getValues;
            Initialize(null, null, logger);
        }

        public StrongWrapper(CIP4ItemAttribute itemAttribute, GetConnectionValuesDelegate getValues, LoggerDelegate logger)
        {
            GetValues = getValues;
            Initialize(itemAttribute, null, logger);
        }

        public StrongWrapper(CIP4ItemAttribute itemAttribute, LoggerDelegate logger)
        {
            Initialize(itemAttribute, null, logger);
        }

        public StrongWrapper(CIP4ConnectionAttribute connection, LoggerDelegate logger)
        {
            Initialize(null, connection, logger);
        }

        public StrongWrapper(CIP4ItemAttribute itemAttribute, CIP4ConnectionAttribute connection, LoggerDelegate logger)
        {
            Initialize(itemAttribute, connection, logger);
        }

        public override ISerializerType GetSerializer(SerializerKindEnum serializerKind)
        {
            if (serializerKind == SerializerKindEnum.CIP4Json)
            {
                if (_jsonSerializer == null)
                {
                    _jsonSerializer = new WrapperSerializerType(SerializerKindEnum.CIP4Json, typeof(TAType), SerializerLocation, ItemAttribute, Logger);
                }
                ((WrapperSerializerType)_jsonSerializer).CheckVersion = CheckItemVersionDuringSave;

                // do this on each call to pickup any local loggers
                ((WrapperSerializerType)_jsonSerializer).SetLogger(Logger);
                return _jsonSerializer;
            }
            {
                if (_xmlSerializer == null)
                {
                    _xmlSerializer = new WrapperSerializerType(SerializerKindEnum.CIP4Xml, typeof(TAType), SerializerLocation, ItemAttribute, Logger);
                }
                ((WrapperSerializerType)_xmlSerializer).CheckVersion = CheckItemVersionDuringSave;

                // do this on each call to pickup any local loggers
                ((WrapperSerializerType)_xmlSerializer).SetLogger(Logger);
                return _xmlSerializer;
            }
        }

        public void ResetProvider()
        {
            _provider = null;
        }

        public void ResetProvider(CIP4ConnectionAttribute connection)
        {
            _constructorConnectionAttribute = connection;
            _provider = null;
        }

        public IEnumerable<TAType> GetItems(IBooleanValue searchExpression)
        {
            return GetItems(searchExpression, Logger);
        }

        public IEnumerable<TAType> GetItems(IBooleanValue searchExpression, LoggerDelegate localLogger)
        {
            using (new LoggerSwapperType(localLogger, this))
            {
                CursorType cursor = 
                    new CursorType(GetSerializer)
                    {
                        PageNumber = 1,
                        PageSize = PageSize,
                        Logger = localLogger,
                        FailOnLinearSearch = QueryShouldFailOnLinerSearch,
                        Marker = string.Empty
                    };
                while (true)
                {
                    int itemCount = 0;
                    WrapperStats.TotalGetTimeStart();
                    foreach (
                        object item in GetProvider(localLogger).GetItems(WrapperCollection, searchExpression, cursor))
                    {
                        WrapperStats.TotalGetTimeStop();
                        WrapperStats.TotalGetCountIncrement();
                        itemCount++;
                        cursor.RecordCount++;
                        yield return (TAType) item;
                        WrapperStats.TotalGetTimeStart();
                    }
                    WrapperStats.TotalGetTimeStop();
                    // loop terminates when the provider return some number of items less than the page size (zero will do)
                    if (itemCount < cursor.PageSize)
                        break;
                    cursor.PageNumber += 1;
                }
                yield break;
            }
        }

        public TAType GetViaExternalID(ExternalIDType externalID)
        {
            return GetViaExternalID(externalID, Logger);
        }

        public TAType GetViaExternalID(ExternalIDType externalID, LoggerDelegate localLogger)
        {
            if (externalID == null || string.IsNullOrEmpty(externalID.ToString()))
                return default(TAType);
           
            BooleanExpressionType selectionExpression = new BooleanExpressionType(
                BooleanOperatorsEnum.EQL,
                new List<CIValueType> {
                    new StringAtomType(externalID.ToString()),
                    new StringAtomType(AtomSourceEnum.namedValue, "ExternalID")});

            return GetItems(selectionExpression, localLogger).FirstOrDefault();
        }

        public void PurgeItems(IEnumerable<ExternalIDType> externalIDs)
        {
            PurgeItems(externalIDs, Logger);
        }

        public void PurgeItems(IEnumerable<ExternalIDType> externalIDs, LoggerDelegate localLogger)
        {
            using (new LoggerSwapperType(localLogger, this))
            {
                IDataProvider provider = GetProvider(localLogger);
                WrapperStats.TotalPurgeTimeStart();
                provider.PurgeItems(externalIDs, WrapperCollection, GetSerializer);
                WrapperStats.TotalPurgeCountIncrement(externalIDs.Count());
                WrapperStats.TotalPurgeTimeStop();
                provider.Terminate(localLogger);
            }
        }

        public void PurgeItems(IBooleanValue searchExpression, LoggerDelegate localLogger)
        {
            using (new LoggerSwapperType(localLogger, this))
            {
                CursorType cursor =
                    new CursorType(GetSerializer)
                    {
                        PageNumber = 1,
                        PageSize = PageSize,
                        Logger = localLogger,
                        FailOnLinearSearch = QueryShouldFailOnLinerSearch,
                        Marker = string.Empty
                    };
                IDataProvider provider = GetProvider(localLogger);
                WrapperStats.TotalPurgeTimeStart();
                provider.PurgeItems(searchExpression, WrapperCollection, cursor);
                WrapperStats.TotalPurgeCountIncrement((int)cursor.RecordCount);
                WrapperStats.TotalPurgeTimeStop();
                provider.Terminate(localLogger);
            }
        }

        public void DeleteCollection()
        {
            DeleteCollection(Logger);
        }

        public void DeleteCollection(LoggerDelegate localLogger)
        {
            using (new LoggerSwapperType(localLogger, this))
            {
                IDataProvider provider = GetProvider(localLogger);
                provider.DeleteCollection(WrapperCollection, localLogger);
                provider.Terminate(localLogger);
            }
        }

        public void SaveItems(IEnumerable<TAType> items)
        {
            SaveItems(items, Logger);
        }

        public void SaveItems(IEnumerable<TAType> items, LoggerDelegate localLogger)
        {
            using (new LoggerSwapperType(localLogger, this))
            {
                IDataProvider provider = GetProvider(localLogger);
                WrapperStats.TotalSaveTimeStart();
                provider.SaveItems(new ObjectEnumerable(items, WrapperStats), WrapperCollection, GetSerializer);
                WrapperStats.TotalSaveTimeStop();
                provider.Terminate(localLogger);
            }
        }

        public string GetXmlForObject(TAType forObject)
        {
            return (string)GetSerializer(SerializerKindEnum.CIP4Xml).GetDataForObject(forObject);
        }

        public TAType GetObjectForXml(XElement element)
        {
            return (TAType)GetSerializer(SerializerKindEnum.CIP4Xml).GetObjectForData(element);
        }

        public object GetJsonForObject(TAType forObject)
        {
            return GetSerializer(SerializerKindEnum.CIP4Json).GetDataForObject(forObject);
        }

        public TAType GetObjectForJson(string jsonText)
        {
            return (TAType)GetSerializer(SerializerKindEnum.CIP4Json).GetObjectForData(jsonText);
        }

        public SortedDictionary<long, TAType> GetItemVersions(TAType forItem, SerializerKindEnum serializerKind)
        {
            SortedDictionary<long, TAType> versions = new SortedDictionary<long, TAType>();

            PropertyInfo versionProperty = GetVersionProperty();

            CIP4ItemVersionsType itemVersions = (CIP4ItemVersionsType)versionProperty.GetValue(forItem, new object[] { });

            TAType lastVersion = forItem;

            foreach (KeyValuePair<long, CIP4ItemVersionType> kvp in itemVersions.Versions.Reverse())
            {
                lastVersion = (TAType)GetSerializer(serializerKind).GetItemVersion(kvp.Key, lastVersion, kvp.Value.Values);
                versions.Add(kvp.Key, lastVersion);
            }

            Thread.Sleep(1);
            long versionTicks = DateTime.UtcNow.Ticks;
            if (!versions.ContainsKey(versionTicks))
                versions.Add(versionTicks, forItem);
            else
                throw new ApplicationException();

            return versions;
        }

        /// <summary>
        /// A helper function that clients can call if they have a list of items they want to delete
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        public IEnumerable<ExternalIDType> ExternalIdsForItems(IEnumerable<TAType> items)
        {
            foreach (TAType item in items)
                yield return GetSerializer(SerializerKindEnum.CIP4Xml).GetExternalID(item);
            yield break;
        }

        public override IEnumerable<ExternalIDType> ExternalIdsForItems(IEnumerable<object> items)
        {
            foreach(object item in items)
                yield return GetSerializer(SerializerKindEnum.CIP4Xml).GetExternalID(item);
            yield break;
        }
        #endregion

        #region IWeakWrapper Members

        public IEnumerable<object> GetBaseItems(IBooleanValue selectionExpression, LoggerDelegate logger)
        {
            CursorType cursor = new CursorType(GetSerializer)
            {
                PageNumber = 1,
                PageSize = 1000,
                Logger = logger,
                FailOnLinearSearch = QueryShouldFailOnLinerSearch
            };
            foreach (object item in GetProvider(logger).GetItems(WrapperCollection, selectionExpression, cursor))
            {
                yield return item;
            }
        }

        public void PurgeBaseItems(IEnumerable<ExternalIDType> externalIDs, LoggerDelegate logger)
        {
            IDataProvider provider = GetProvider(logger);
            provider.PurgeItems(externalIDs, WrapperCollection, GetSerializer);
            provider.Terminate(logger);
        }

        public void SaveBaseItems(IEnumerable<object> items, LoggerDelegate logger)
        {
            IDataProvider provider = GetProvider(logger);
            provider.SaveItems(items, WrapperCollection, GetSerializer);
            provider.Terminate(logger);
        }

        #endregion

        #region Privates

        private PropertyInfo _versionProperty;
        private bool _alreadyLookedForAVersionProperty;
        private PropertyInfo GetVersionProperty()
        {
            if (!_alreadyLookedForAVersionProperty && _versionProperty == null)
                _versionProperty = typeof(TAType).GetProperties().FirstOrDefault(p => p.PropertyType.Name == typeof(CIP4ItemVersionsType).Name);
            _alreadyLookedForAVersionProperty = true;
            return _versionProperty;
        }

        private void Initialize(CIP4ItemAttribute itemAttribute, CIP4ConnectionAttribute connection, LoggerDelegate logger)
        {
            if (logger == null)
                throw new ArgumentException("You must provide a logger");
            Logger = logger;
            PageSize = 1000;
            Type theType = typeof(TAType);

            if (itemAttribute != null)
                ItemAttribute = itemAttribute;

            if (connection != null)
                _constructorConnectionAttribute = connection;

            if (ItemAttribute != null && string.IsNullOrEmpty(ItemAttribute.ActualName))
                ItemAttribute.ActualName = theType.Name;

            WrapperCollection = new CollectionType
            {
                Name = WrapperCollectionName,
                CollectionCompoundType = theType,
                ExternalID = new ExternalIDType(WrapperCollectionName)
            };

#if ENABLECONTROLLER
            WrapperController();
#endif
        }

        class ObjectEnumerable : IEnumerable<object>
        {
            readonly IEnumerable<TAType> _objects;
            readonly WrapperStatsType _wrapperStats;
            internal ObjectEnumerable(IEnumerable<TAType> items, WrapperStatsType wrapperStats)
            {
                _objects = items;
                _wrapperStats = wrapperStats;
            }

            #region IEnumerable<object> Members

            public IEnumerator<object> GetEnumerator()
            {
                return new ObjectEnumerator(_objects, _wrapperStats);
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return new ObjectEnumerator(_objects, _wrapperStats);
            }

            #endregion
        }

        class ObjectEnumerator: IEnumerator<object>
        {
            readonly IEnumerator<TAType> _objects;

            readonly WrapperStatsType _wrapperStats;

            internal ObjectEnumerator(IEnumerable<TAType> items, WrapperStatsType wrapperStats)
            {
                _wrapperStats = wrapperStats;
                _objects = items.GetEnumerator();
            }

            #region IEnumerator<object> Members

            public object Current
            {
                get { return _objects.Current; }
            }

            #endregion

            #region IEnumerator Members

            public bool MoveNext()
            {
                while (_objects.MoveNext())
                {
// ReSharper disable CompareNonConstrainedGenericWithNull
                    if ( _objects.Current != null)
// ReSharper restore CompareNonConstrainedGenericWithNull
                    {
                        _wrapperStats.TotalSaveCountIncrement();
                        return true;
                    }
                }
                return false;
            }

            public void Reset()
            {
                _objects.Reset();
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                _objects.Dispose();
            }

            #endregion

        }

        private class LoggerSwapperType : IDisposable
        {
            private readonly LoggerDelegate _savedLogger;
            private readonly StrongWrapper<TAType> _wrapper;
            public LoggerSwapperType(LoggerDelegate logger, StrongWrapper<TAType> wrapper)
            {
                _savedLogger = wrapper.Logger;
                if (logger != null)
                    wrapper.Logger = logger;
                _wrapper = wrapper;
            }

            #region IDisposable Members

            public void Dispose()
            {
                _wrapper.Logger = _savedLogger;
            }

            #endregion
        }

        // We're a little disorganized here at the moment - the idea is if someone adds a new type of serializer, need 
        // to handle it here. GetSerializer should return a serializer of the requested type. Probably caching them 
        // on the assumption that constructing serializer ain't likely to be cheap and GetSerializer may get called a lot
        ISerializerType _xmlSerializer;
        ISerializerType _jsonSerializer;
        internal void SetSerializer(ISerializerType serilizer, SerializerKindEnum serializerKind)
        {
            if (serializerKind == SerializerKindEnum.CIP4Json)
            {
                _jsonSerializer = serilizer;
            }
            else
            {
                _xmlSerializer = serilizer;
            }
        }

        private IDataProvider _provider;
        /// <summary>
        /// If there is one, GetProvider returns the currently loaded provider. If the provider is not curently loaded, 
        /// GetProvider resolves the ConnectionAttribute and calls DataProvider.GetProvider to load the provider. 
        /// The DataProvider.GetProvider call result is cached. The Cache can be reset by calling ResetProvider().
        /// </summary>
        /// <param name="logger"></param>
        /// <returns></returns>
        private IDataProvider GetProvider(LoggerDelegate logger)
        {

#if ENABLECONTROLLER
            PauseEvent.WaitOne();
#endif
            if (_provider == null)
            {
                IDataProvider cacheProvider = null;
                if (WellConnection.Cache != null)
                {
                    cacheProvider = DataProvider.GetProvider(WellConnection.Cache, null, Logger);
                    ((DataProvider)cacheProvider).IsACache = true;
                }
                _provider = DataProvider.GetProvider(WellConnection, cacheProvider, logger);
                if (_provider.GetType().GetProperty("WrapperBase") != null)
                    _provider.GetType().GetProperty("WrapperBase").SetValue(_provider, this, new object[] { });
            }
            return _provider;
        }

        #region Wrapper Controller Related

        public readonly WrapperStatsType WrapperStats = new WrapperStatsType();

#if ENABLECONTROLLER

        private EventWithVisibleStateType PauseEvent = new EventWithVisibleStateType();

        private CommandFileType WatchCommand;

        private string GetCommandFileName()
        {
            if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CIP4")))
                Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CIP4"));
            return Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), 
                "CIP4\\" + this.WrapperCollection.Name + "." + Environment.TickCount.ToString() + ".command.txt");
        }

        private void WrapperController()
        {
            WatchCommand = new CommandFileType("Wrapper", GetCommandFileName(), WrapperCollection.Name, new FileSystemEventHandler(WatchCommand_Changed));
            WatchCommand.Start();
        }

        void WatchCommand_Changed(object sender, FileSystemEventArgs e)
        {
            if (WatchCommand.WritingCommand)
                return;
            string command = WatchCommand.ReadCommand().ToLower();
            switch (command)
            {
                case "status":
                    WatchCommand.WriteCommand(string.Format("Collection={0}, Type={1}, PauseEvent={2}, Gets={3},Saves={4},Purges={5},DataProvider={6}", 
                        WrapperCollection.Name, 
                        WrapperCollection.CollectionCompoundType.Name,
                        PauseEvent.ToString(),
                        WrapperStats.Counters[(int)WrapperStatsType.StatsTypesEnum.Get][(int)WrapperStatsType.StatsCounterTypeEnum.count].RawValue,
                        WrapperStats.Counters[(int)WrapperStatsType.StatsTypesEnum.Save][(int)WrapperStatsType.StatsCounterTypeEnum.count].RawValue,
                        WrapperStats.Counters[(int)WrapperStatsType.StatsTypesEnum.Purge][(int)WrapperStatsType.StatsCounterTypeEnum.count].RawValue,
                        this.WellConnection.ToString()));
                    break;
                case "pause":
                    PauseEvent.Reset();
                    Logger(MajorEnum.Trace, MinorEnum.Generic, "Pause command received for {0}", new object[] { WrapperCollection.Name });
                    break;
                case "resume":
                    PauseEvent.Set();
                    Logger(MajorEnum.Trace, MinorEnum.Generic, "Resume command received for {0}", new object[] { WrapperCollection.Name });
                    break;
                case "cycle":
                    ResetProvider();
                    Logger(MajorEnum.Trace, MinorEnum.Generic, "Cycle Provider command received for {0}", new object[] { WrapperCollection.Name });
                    break;
                default:
                    //WatchCommand.WriteCommand("Unrecognized command - " + command);
                    break;
            }
        }

        class EventWithVisibleStateType
        {
            ManualResetEvent TheEvent = new ManualResetEvent(true);

            bool IsSet = true;

            public void Set()
            {
                TheEvent.Set();
                IsSet = true;
            }

            public void Reset()
            {
                TheEvent.Reset();
                IsSet = false;
            }

            public void WaitOne()
            {
                TheEvent.WaitOne();
            }

            public override string ToString()
            {
                return IsSet? "Not Paused" : "Paused";
            }
        }

#endif

        #endregion

        #endregion

        public void Dispose(bool calledFromDispose)
        {
            Logger(MajorEnum.Trace, MinorEnum.Generic, "Exiting wrapper for {0}", new object[] { WrapperCollection.Name });
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
        }

        #endregion
    }
}
